package theeg.DependencyAnalysis.Core;

import LEX.Queryable;
import theeg.DependencyAnalysis.Core.DataModel.*;
import theeg.DependencyAnalysis.Core.Internal.Methodenreinheit;

import static LEX.LanguageExtensions.*;

import java.util.*;

public abstract class Metrics {
    private final HalsteadCache _halsteadCache = new HalsteadCache();

    private final DependencyModel _model;

    protected Metrics(DependencyModel model) {
        _model = model;
    }

    /// <summary>
    /// With the number of referrers, then the impact of changing API and implementation grows.
    /// Return the number of types in other modules than the target that refer to the target module.
    /// </summary>
    /// <param name="target">a module</param>
    /// <returns>number of referred types</returns>
    public int AfferentCoupling(ModuleDescriptor target) {
        if (!_model.getDependenciesPerModule().containsKey(target)) return 0;
        List types = VerifierUtilities.aggregateDependenciesToTypes(_model.getDependenciesPerModule().get(target), true);
        return types.size();
    }

    /// <summary>
    /// With the number of referrers, then the impact of changing API and implementation grows.
    /// Return the number of types in other submodules than the target that refer to the target submodule.
    /// </summary>
    /// <param name="target">a submodule</param>
    /// <returns>number of referred types</returns>
    public int AfferentCoupling(SubmoduleDescriptor target) {
        if (!_model.getDependenciesPerSubmodule().containsKey(target)) return 0;
        List types = VerifierUtilities.aggregateDependenciesToTypes(_model.getDependenciesPerSubmodule().get(target), true);
        return types.size();
    }

    /// <summary>
    /// With the number of referrers, then the impact of changing member signatures and implementation grows.
    /// Return the number of types that refer to the examined type. Self references are excluded.
    /// </summary>
    /// <param name="target">a type</param>
    /// <returns>number of referred types</returns>
    public int AfferentCoupling(TypeDescriptor target) {
        if (!_model.getDependenciesPerType().containsKey(target)) return 0;
        List types = VerifierUtilities.aggregateDependenciesToTypes(_model.getDependenciesPerType().get(target), true);
        return types.size();
    }

    /// <summary>
    /// With the number of referrers, then the impact of changing the members signature and implementation grows.
    /// Return the number of members that refer to the examined member. Self references are excluded.
    /// </summary>
    /// <param name="target">a member</param>
    /// <returns>number of referred members</returns>
    public int AfferentCoupling(MemberDescriptor target) {
        if (!_model.getDependenciesPerMember().containsKey(target)) return 0;
        return _model.getDependenciesPerMember().get(target).size();
    }

    /// <summary>
    /// Return the number of types the examined module depends on. The type inside the module are excluded.
    /// </summary>
    /// <param name="target">a module</param>
    /// <returns>number of referred types</returns>
    public int EfferentCoupling(ModuleDescriptor target) {
        if (!_model.getDependenciesPerModuleOut().containsKey(target)) return 0;
        List types = VerifierUtilities.aggregateDependenciesToTypes(_model.getDependenciesPerModuleOut().get(target), false);
        return types.size();
    }

    /// <summary>
    /// Return the number of types the examined submodule depends on. The type inside the submodule are excluded.
    /// </summary>
    /// <param name="target">a submodule</param>
    /// <returns>number of referred types</returns>
    public int EfferentCoupling(SubmoduleDescriptor target) {
        if (!_model.getDependenciesPerSubmoduleOut().containsKey(target)) return 0;
        List types = VerifierUtilities.aggregateDependenciesToTypes(_model.getDependenciesPerSubmoduleOut().get(target), false);
        return types.size();
    }

    /// <summary>
    /// Return the number of types the examined type depends on. Self references are excluded.
    /// </summary>
    /// <param name="target">a type</param>
    /// <returns>number of referred types</returns>
    public int EfferentCoupling(TypeDescriptor target) {
        if (!_model.getDependenciesPerTypeOut().containsKey(target)) return 0;
        List types = VerifierUtilities.aggregateDependenciesToTypes(_model.getDependenciesPerTypeOut().get(target), false);
        return types.size();
    }

    /// <summary>
    /// Return the number of members the examined member depends on. The declaring type is excluded.
    /// </summary>
    /// <param name="target">a member</param>
    /// <returns>number of referred members</returns>
    public int EfferentCoupling(MemberDescriptor target) {
        if (!_model.getDependenciesPerMemberOut().containsKey(target)) return 0;
        return _model.getDependenciesPerMemberOut().get(target).size();
    }

    /// <summary>
    /// The average number of relations between types of the module per type.
    /// Optimal values lie between 1.5 and 4.0.
    /// RelationalCohesion = (RelationShips + 1) / Types
    /// </summary>
    /// <param name="target">a module</param>
    /// <returns></returns>
    public double RelationalCohesion(ModuleDescriptor target) {
        if (!_model.getDependenciesPerModule().containsKey(target)) return 0;
        int relationships = 0;
        List<TypeDescriptor> typesInModule = Queryable.From(target.getSubmodules()).SelectMany(space -> space.getTypes()).toList();
        if (typesInModule.size() == 0) return 0;
        for (TypeDescriptor type : typesInModule) {
            Queryable<DependencyDescriptor> dependencies = Queryable.From(_model.getDependenciesPerType().get(type));
            List internalReferences = dependencies.Where(dependency -> dependency.getSource().getImplementingType().getSubmodule().getModule().equals(target)).toList();
            List types = VerifierUtilities.aggregateDependenciesToTypes(internalReferences, true);
            relationships += types.size();
        }
        return (relationships + 1) / (double) typesInModule.size();
    }

    /// <summary>
    /// Modules that contain multiple outgoing but few incoming dependencies are less stable because of the consequences of changes in these Modules.
    /// Modules containing more incoming dependencies are more stable because they are more difficult to change.
    /// Instability = EfferentCoupling / (EfferentCoupling + AfferentCoupling)
    /// </summary>
    /// <param name="target">a submodule</param>
    /// <returns>[0, 1]</returns>
    public double Instability(ModuleDescriptor target) {
        if (!_model.getDependenciesPerModule().containsKey(target)) return 0;
        int efferent = EfferentCoupling(target);
        int afferent = AfferentCoupling(target);
        if (efferent + afferent == 0) return 1;
        return efferent / (double) (efferent + afferent);
    }

    /// <summary>
    /// The ratio of abstract and interface types in a module.
    /// </summary>
    /// <param name="target">a module</param>
    /// <returns>[0, 1]</returns>
    public double Abstractness(ModuleDescriptor target) {
        if (!_model.getDependenciesPerModule().containsKey(target)) return 0;
        List<TypeDescriptor> typesInModule = Queryable.From(target.getSubmodules()).SelectMany(space -> space.getTypes()).toList();
        if (typesInModule.size() == 0) return 0;
        AbstractionType compareAbstraction = new AbstractionType(AbstractionType.Interface, AbstractionType.Abstract);
        int abstractCount = Queryable.From(typesInModule).Count(typeDescriptor ->
                typeDescriptor.getAbstractionType().And(compareAbstraction));
        return abstractCount / (double) typesInModule.size();
    }

    /// <summary>
    /// Describes the balance between abstractness and stability of a module. A module becomes stable when there are a lot of
    /// referrers to it. If so, then it should be abstract at the same time. A module that is instable should be concrete, as there
    /// are no referrers relying on contracts.
    /// Values are in range [-1, +1]. Close to -1 means the module is concrete but has many dependent modules, changes to the module will
    /// imply lots of changes to referrers. This is called the zone of pain. On the other side, a value of one means the module is
    /// almost completely abstract, but noone uses it. Which is the zone of uselessness.
    /// NormalizedDistance = Abstractness + Instability - 1
    /// </summary>
    /// <param name="target">a module</param>
    /// <returns>[-1, +1]</returns>
    public double DistanceFromMainSequence(ModuleDescriptor target) {
        double abstractness = Abstractness(target);
        double instability = Instability(target);
        return abstractness + instability - 1;
    }

    /// <summary>
    /// The average number of relations between types of the submodule per type.
    /// Optimal values lie between 1.5 and 4.0.
    /// RelationalCohesion = (RelationShips + 1) / Types
    /// </summary>
    /// <param name="target">a submodule</param>
    /// <returns></returns>
    public double RelationalCohesion(SubmoduleDescriptor target) {
        if (!_model.getDependenciesPerSubmodule().containsKey(target)) return 0;
        List<DependencyDescriptor> internalReferences = new ArrayList<>();
        List<TypeDescriptor> typesInSpaces = target.getTypes();
        if (typesInSpaces.size() == 0) return 0;
        int relationships = 0;
        for (TypeDescriptor type : typesInSpaces) {
            Queryable<DependencyDescriptor> dependencies = Queryable.From(_model.getDependenciesPerType().get(type));
            internalReferences.addAll(dependencies.Where(dependency -> dependency.getSource().getImplementingType().getSubmodule().equals(target)).toList());
            List types = VerifierUtilities.aggregateDependenciesToTypes(internalReferences, true);
            relationships += types.size();
        }
        return (relationships + 1) / (double) typesInSpaces.size();
    }

    /// <summary>
    /// Submodules that contain multiple outgoing but few incoming dependencies are less stable because of the consequences of changes in these submodules.
    /// Submodules containing more incoming dependencies are more stable because they are more difficult to change.
    /// Instability = EfferentCoupling / (EfferentCoupling AfferentCoupling)
    /// </summary>
    /// <param name="target">a submodule</param>
    /// <returns>[0, 1]</returns>
    public double Instability(SubmoduleDescriptor target) {
        if (!_model.getDependenciesPerSubmodule().containsKey(target)) return 0;
        int efferent = EfferentCoupling(target);
        int afferent = AfferentCoupling(target);
        if (efferent + afferent == 0) return 1;
        return efferent / (double) (efferent + afferent);
    }

    /// <summary>
    /// The ratio of abstract and interface types in a submodule.
    /// </summary>
    /// <param name="target">a submodule</param>
    /// <returns>[0, 1]</returns>
    public double Abstractness(SubmoduleDescriptor target) {
        if (!_model.getDependenciesPerSubmodule().containsKey(target)) return 0;
        List<TypeDescriptor> typesInModule = target.getTypes();
        if (typesInModule.size() == 0) return 0;
        AbstractionType compareAbstraction = new AbstractionType(AbstractionType.Interface, AbstractionType.Abstract);
        int abstractCount = Queryable.From(typesInModule).Count(typeDescriptor ->
                typeDescriptor.getAbstractionType().And(compareAbstraction));
        return abstractCount / (double) typesInModule.size();
    }

    /// <summary>
    /// Describes the balance between abstractness and stability of a submodule. A submodule becomes stable when there are a lot of
    /// referrers to it. If so, then it should be abstract at the same time. A submodule that is instable should be concrete, as there
    /// are no referrers relying on contracts.
    /// Values are in range [-1, +1]. Close to -1 means the submodule is concrete but has many dependent submodules, changes to the submodule will
    /// imply lots of changes to referrers. This is called the zone of pain. On the other side, a value of one means the submodule is
    /// almost completely abstract, but noone uses it. Which is the zone of uselessness.
    /// NormalizedDistance = Abstractness + Instability - 1
    /// </summary>
    /// <param name="target">a submodule</param>
    /// <returns>[-1, +1]</returns>
    public double DistanceFromMainSequence(SubmoduleDescriptor target) {
        double abstractness = Abstractness(target);
        double instability = Instability(target);
        return abstractness + instability - 1;
    }

    /// <summary>
    /// Lack of Cohesion Of Methods (LCOM): The single responsibility principle states that a class should not have more than one reason to change.
    /// Such a class is said to be cohesive. A high LCOM value generally pinpoints a poorly cohesive class.
    /// The LCOM HS (HS stands for Henderson-Sellers) takes its values in the range [0-2].
    /// A LCOM HS value higher than 1 should be considered alarming.
    /// </summary>
    /// <param name="target">a type</param>
    /// <returns>[0, 2]</returns>
    public double LackOfCohesion(TypeDescriptor target) {
        if (!_model.getDependenciesPerType().containsKey(target)) return 0;
        int numberOfMethods = NumberOfMethods(target);
        List<Integer> internalAccessors = new ArrayList<>();
        for (MemberDescriptor field : Queryable.From(target.getMembers()).Where(x -> x.getMemberType() == MemberType.Field)) {
            Set<DependencyDescriptor> afferences = _model.getDependenciesPerMember().get(field);
            int accessors = 0;
            for (DependencyDescriptor dependency : afferences) {
                if (!dependency.getSource().getImplementingType().equals(target)) continue;
                accessors++;
            }
            internalAccessors.add(accessors);
        }
        double averageAccessors = 0;
        if (internalAccessors.size() > 0) {
            int sum = 0;
            for (Integer accessorCount : internalAccessors) {
                sum += accessorCount;
            }
            averageAccessors = sum / (double) internalAccessors.size();
        }
        if (numberOfMethods == 1) numberOfMethods = 0;
        return (averageAccessors - numberOfMethods) / (1.0 - numberOfMethods);
    }

    /// <summary>
    /// The sum of the cyclomatic complexity of all members from that type.
    /// </summary>
    /// <param name="target">a type</param>
    /// <returns></returns>
    public int CyclomaticComplexity(TypeDescriptor target) {
        return Queryable.From(target.getMembers()).Where(member -> member.getMemberType() == MemberType.Method).Sum(member -> CyclomaticComplexity(member));
    }

    /// <summary>
    /// Measures the number of independent linear paths through a member. Fields return 0.
    /// </summary>
    /// <param name="target">a member</param>
    /// <returns></returns>
    public int CyclomaticComplexity(MemberDescriptor target) {
        if (target.getMemberType() != MemberType.Method) return 0;
        List<Instruction> instructions = _model.getInstructionCache().getInstructions(target);
        return Queryable.From(instructions).Count(instruction -> InstructionCache.CyclomaticInstructions.contains(instruction.getOpCode()));
    }


    /// <summary>
    /// Count the number of fields in a type.
    /// </summary>
    /// <param name="target">a type</param>
    /// <returns></returns>
    public int NumberOfFields(TypeDescriptor target) {
        return Queryable.From(target.getMembers()).Count(x -> x.getMemberType() == MemberType.Field);
    }

    /// <summary>
    /// Count the number of methods in a type.
    /// </summary>
    /// <param name="target">a type</param>
    /// <returns></returns>
    public int NumberOfMethods(TypeDescriptor target) {
        //TODO ignore accessors like getter and setter, but include false named methods (identify by searching for an equally named field)
        return Queryable.From(target.getMembers()).Count(x -> x.getMemberType() == MemberType.Method &&
                !x.getName().startsWith("get_") && !x.getName().startsWith("set_"));
    }

    /// <summary>
    /// Count the number of methods in a type that are overriding a member of a base class.
    /// </summary>
    /// <param name="target">a type</param>
    /// <returns></returns>
    public int NumberOfOverriddenMethods(TypeDescriptor target) {
        return Queryable.From(target.getMembers()).Count(x -> x.getMemberType() == MemberType.Method && x.isOverride());
    }

    /// <summary>
    /// Count the number of types that inherit from the target type.
    /// </summary>
    /// <param name="target"></param>
    /// <returns></returns>
    public int NumberOfChildren(TypeDescriptor target) {
        return target.getImplementingTypes() == null ? 0 : target.getImplementingTypes().size();
    }

    /// <summary>
    /// The deeper the inheritance graph and the more members are overridden methods, the more complex
    /// a system gets. A high value of specialization is an indicator for lack of maintainability for a
    /// type and its callers.
    /// SIX = NumberOfOverriddenMethods * DepthOfInheritance / Max(NumberOfMethods, 1)
    /// A value above 1.2 should be considered alarming.
    /// </summary>
    /// <param name="target">a type</param>
    /// <returns>[0, +)</returns>
    public double SpecializationIndex(TypeDescriptor target) {
        double numberOfMethods = Math.max(1, NumberOfMethods(target));
        return NumberOfOverriddenMethods(target) * DepthOfInheritance(target) / numberOfMethods;
    }

    /// <summary>
    /// Sum of the instructions of all members
    /// </summary>
    /// <param name="target">a member</param>
    /// <returns></returns>
    public int NumberOfInstructions(TypeDescriptor target) {
        return Queryable.From(target.getMembers()).Where(member -> member.getMemberType() == MemberType.Method).Sum(member -> NumberOfInstructions(member));
    }

    /// <summary>
    /// Count the number of intermediate code instructions of a member.
    /// Fields return 0.
    /// </summary>
    /// <param name="target">a member</param>
    /// <returns></returns>
    public int NumberOfInstructions(MemberDescriptor target) {
        if (target.getMemberType() != MemberType.Method) return 0;
        List instructions = _model.getInstructionCache().getInstructions(target);
        return instructions.size();
    }

    /// <summary>
    /// Return the depth of the inheritance tree from this type to System.Object inclusive.
    /// Interfaces do not count. Only Abstract and concrete types.
    /// The deeper a class in the hierarchy, the greater the number of methods it will probably
    /// inherit which makes it harder to predict its behavior. Deeper trees involve greater
    /// design complexity since more classes and methods are involved. Deeper classes in the
    /// tree have a greater potential for reusing inherited methods.
    /// </summary>
    /// <param name="target"></param>
    /// <returns></returns>
    public int DepthOfInheritance(TypeDescriptor target) {
        int depth = 0;
        TypeDescriptor currentType = target;
        AbstractionType compareAbstraction = new AbstractionType(AbstractionType.Concrete, AbstractionType.Abstract);
        while (currentType != null && currentType.getInheritedTypes() != null) {
            TypeDescriptor selectedType = currentType;
            currentType = null;
            for (TypeDescriptor type : selectedType.getInheritedTypes()) {
                if (!type.getAbstractionType().And(compareAbstraction))
                    continue;
                currentType = type;
                depth++;
                break;
            }
        }
        return depth;
    }

    /// <summary>
    /// The number of members from other types that are directly accessed by the specified type.
    /// </summary>
    /// <param name="target">a type</param>
    /// <returns></returns>
    public int AssociationBetweenClasses(TypeDescriptor target) {
        if (!_model.getDependenciesPerTypeOut().containsKey(target)) return 0;
        Set<DependencyDescriptor> afferences = _model.getDependenciesPerTypeOut().get(target);
        List referredMembers = VerifierUtilities.aggregateDependenciesToMembers(afferences, false);
        return referredMembers.size();
    }

    /// <summary>
    /// Count all interface the current type implements, which equals the number of
    /// different contracts a type has to fulfill. Interfaces on inherited types and interfaces that are
    /// base types for other interfaces also count.
    /// Low values are desired.
    /// </summary>
    /// <param name="target">a type</param>
    /// <returns></returns>
    public int NumberOfInterfaces(TypeDescriptor target) {
        Set<TypeDescriptor> interfaces = new HashSet<>();
        AddInterfacesRecursive(target, interfaces);
        return interfaces.size();
    }

    private static void AddInterfacesRecursive(TypeDescriptor target, Set<TypeDescriptor> interfaces) {
        if (target.getInheritedTypes() == null) return;
        for (TypeDescriptor type : target.getInheritedTypes()) {
            AddInterfacesRecursive(type, interfaces);
            if (!type.getAbstractionType().And(AbstractionType.Interface)) continue;
            if (interfaces.contains(type)) continue;
            interfaces.add(type);
        }
    }

    /// <summary>
    /// Return the deepest nesting of a members IL instructions. An implemented
    /// method at least has a nesting depth of 1 as entering the method counts as scope.
    /// Fields return 0.
    /// Anything above 5 should be considered for refactoring.
    /// </summary>
    /// <param name="target">a member</param>
    /// <returns></returns>
    public int NestingDepth(MemberDescriptor target) {
        if (target == null || target.getMemberType() != MemberType.Method) return 0;
        List<Instruction> instructions = _model.getInstructionCache().getInstructions(target);
        Stack<Instruction> activeBranches = new Stack<>();
        int maximumDepth = 0;
        for (Instruction instruction : instructions) {
            while (activeBranches.size() > 0 && instruction.getOffset() >= (Long) (activeBranches.peek().getOperand())) {
                activeBranches.pop();
            }
            long lastBranch = (activeBranches.size() > 0) ? (long) (activeBranches.peek().getOperand()) : 0;
            if (!InstructionCache.NestingInstructions.contains(instruction.getOpCode())) continue;
            if (lastBranch > 0 && (Long) instruction.getOperand() >= lastBranch) continue;
            activeBranches.push(instruction);
            maximumDepth = Math.max(maximumDepth, activeBranches.size());
        }
        return maximumDepth + 1;
    }

    /// <summary>
    /// Count the number of parameters of a method. For more than 2 parameters you must have good reasons.
    /// More than 3 parameters might be caused by implementing framework interfaces. Otherwise having more
    /// than 3 parameters is a hint for braking the single responsibility principle.
    /// </summary>
    /// <param name="target">a member</param>
    /// <returns></returns>
    public int NumberOfParameters(MemberDescriptor target) {
        return target.getParameterCount();
    }

    /// <summary>
    /// Counts the number of references from one architectural component to another. Both source and target can
    /// be modules, submodules, types or members.
    /// </summary>
    /// <param name="source">source of the reference</param>
    /// <param name="target"></param>
    /// <returns></returns>
    public int NumberOfReferences(Descriptor source, final Descriptor target) {
        Class sourceType = source.getClass();
        Descriptor targetCopy = target;
        Set<DependencyDescriptor> descriptors = null;
        if (sourceType.equals(ModuleDescriptor.class)) {
            descriptors = _model.getDependenciesPerModuleOut().getOrDefault(source, null);
        } else if (sourceType.equals(SubmoduleDescriptor.class)) {
            descriptors = _model.getDependenciesPerSubmoduleOut().getOrDefault(source, null);
        } else if (sourceType.equals(TypeDescriptor.class)) {
            descriptors = _model.getDependenciesPerTypeOut().getOrDefault(source, null);
        } else if (sourceType.equals(MemberDescriptor.class)) {
            descriptors = _model.getDependenciesPerMemberOut().getOrDefault(source, null);
        } else {
            throwUnchecked(new IllegalArgumentException("source"));
        }
        if (descriptors == null) {
            return 0;
        }

        Class targetType = target.getClass();
        Queryable<DependencyDescriptor> descriptorQueryable = Queryable.From(descriptors);
        if (targetType.equals(ModuleDescriptor.class)) {
            return descriptorQueryable.Count(x -> target.equals(x.getTarget().getImplementingType().getSubmodule().getModule()));
        }
        if (targetType.equals(SubmoduleDescriptor.class)) {
            return descriptorQueryable.Count(x -> target.equals(x.getTarget().getImplementingType().getSubmodule()));
        }
        if (targetType.equals(TypeDescriptor.class)) {
            Set<TypeDescriptor> variants = new HashSet<>();
            TypeDescriptor targetCasted = (TypeDescriptor) target;

            if (targetCasted.getSubmodule() == null) {
                TypeDescriptor foundInModel = Queryable.From(_model.getDependenciesPerType().keySet()).FirstOrDefault(x -> targetCasted.equals(x));
                if (foundInModel == null) {
                    return 0;
                }
                targetCopy = foundInModel;
            }

            Methodenreinheit.addVariants((TypeDescriptor) targetCopy, variants, false);
            return descriptorQueryable.Count(x -> variants.contains(x.getTarget().getImplementingType()));
        }
        if (targetType.equals(MemberDescriptor.class)) {
            //TODO overridden members
            return descriptorQueryable.Count(x -> target.equals(x.getTarget()));
        }
        throwUnchecked(new IllegalArgumentException("target"));
        return 0;
    }

    /// <summary>
    /// Counts the number of types defined in a module, aggregating all submodules.
    /// </summary>
    /// <param name="target"></param>
    /// <returns></returns>
    public int NumberOfTypes(ModuleDescriptor target) {
        return target.getSubmodules() == null ? 0 : Queryable.From(target.getSubmodules()).Sum(x -> NumberOfTypes(x));
    }

    /// <summary>
    /// Counts the number of types defined in a submodule.
    /// </summary>
    /// <param name="target"></param>
    /// <returns></returns>
    public int NumberOfTypes(SubmoduleDescriptor target) {
        return target.getTypes() == null ? 0 : target.getTypes().size();
    }

    /// <summary>
    /// Count the number of local variables in a member. Fields return 0.
    /// </summary>
    /// <param name="target">a member</param>
    /// <returns></returns>
    public int NumberOfVariables(MemberDescriptor target) {
        return target.getVariableCount();
    }

    /// <summary>
    /// Returns the basic of all halstead metrics for the part of program. It contains
    /// - total operators
    /// - total operands
    /// - distinct operators
    /// - distinct operands
    /// </summary>
    /// <typeparam name="T">MemberDescriptor, TypeDescriptor, SubmoduleDescriptor, ModuleDescriptor</typeparam>
    /// <param name="target">part of the program</param>
    /// <returns></returns>
    public <T extends Descriptor> HalsteadEntry HalsteadEntry(T target) {
        return _halsteadCache.getCacheEntry(target, _model);
    }

    /// <summary>
    /// Sum of the total number of operators and operands in the part of the program.
    ///
    /// Length = operators + operands
    /// </summary>
    /// <typeparam name="T">MemberDescriptor, TypeDescriptor, SubmoduleDescriptor, ModuleDescriptor</typeparam>
    /// <param name="target">part of the program</param>
    /// <returns></returns>
    public <T extends Descriptor> int HalsteadProgramLength(T target) {
        HalsteadEntry cacheEntry = _halsteadCache.getCacheEntry(target, _model);
        return cacheEntry.getTotalOperators() + cacheEntry.getTotalOperands();
    }

    /// <summary>
    /// Count of the distinct number of operators and operands in the part of the program.
    ///
    /// Vocabulary = ||operators|| + ||operands||
    /// </summary>
    /// <typeparam name="T">MemberDescriptor, TypeDescriptor, SubmoduleDescriptor, ModuleDescriptor</typeparam>
    /// <param name="target">part of the program</param>
    /// <returns></returns>
    public <T extends Descriptor> int HalsteadProgramVocabulary(T target) {
        HalsteadEntry cacheEntry = _halsteadCache.getCacheEntry(target, _model);
        return cacheEntry.getDistinctOperators() + cacheEntry.getDistinctOperands();
    }

    /// <summary>
    /// According to Halstead, Program Volume V corresponds to the minimum number of bits required for program coding.
    ///
    /// Volume = Length * Log2(Vocabulary)
    /// </summary>
    /// <typeparam name="T">MemberDescriptor, TypeDescriptor, SubmoduleDescriptor, ModuleDescriptor</typeparam>
    /// <param name="target"></param>
    /// <returns></returns>
    public <T extends Descriptor> double HalsteadVolume(T target) {
        return HalsteadProgramLength(target) * Math.log((double) HalsteadProgramVocabulary(target)) / Math.log(2.0);
    }

    /// <summary>
    /// The difficulty level or error proneness of the program is proportional to the number of unique operators in the program.
    /// Difficulty is also proportional to the ration between the total number of operands and the number of unique operands (i.e. if the same operands are used many times in the program, it is more prone to errors).
    ///
    /// Difficulty = ( ||operators|| / 2 ) * ( operands / ||operands|| )
    /// </summary>
    /// <typeparam name="T">MemberDescriptor, TypeDescriptor, SubmoduleDescriptor, ModuleDescriptor, ProjectDescriptor</typeparam>
    /// <param name="target">part of the program</param>
    /// <returns></returns>
    public <T extends Descriptor> double HalsteadDifficulty(T target) {
        HalsteadEntry cacheEntry = _halsteadCache.getCacheEntry(target, _model);
        if (cacheEntry.getDistinctOperands() == 0) {
            return 0;
        }
        return 0.5 * cacheEntry.getDistinctOperators() * cacheEntry.getTotalOperands() / cacheEntry.getDistinctOperands();
    }

    /// <summary>
    /// The effort to implement or understand a program is proportional to the volume and to the difficulty level of the program.
    ///
    /// Effort = Volume * Difficulty
    /// </summary>
    /// <typeparam name="T">MemberDescriptor, TypeDescriptor, SubmoduleDescriptor, ModuleDescriptor, ProjectDescriptor</typeparam>
    /// <param name="target">part of the program</param>
    /// <returns></returns>
    public <T extends Descriptor> double HalsteadEffort(T target) {
        return HalsteadDifficulty(target) * HalsteadVolume(target);
    }

    /// <summary>
    /// Halsteads delivered bugs is an estimate for the number of errors in the implementation.
    ///
    /// Bugs = ( Effort ^ (2/3) ) / 3000
    ///
    /// Delivered bugs is calculated for all modules in the current dependency model that are marked with
    /// IsPartOfProject.
    /// </summary>
    /// <returns></returns>
    public double DeliveredBugs() {
        return DeliveredBugs(_model.getProject());
    }

    /// <summary>
    /// Halsteads delivered bugs is an estimate for the number of errors in the implementation.
    ///
    /// Bugs = ( Effort ^ (2/3) ) / 3000
    /// </summary>
    /// <param name="project">modules that form the program</param>
    /// <returns></returns>
    public double DeliveredBugs(ProjectDescriptor project) {
        double effort = HalsteadEffort(project);
        return Math.pow(effort, 2d / 3) / 3000d;
    }

    /// <summary>
    /// The time to implement or understand a program is proportional to the effort.
    /// Halstead has found that dividing the effort by 18 give an approximation for the time in seconds.
    ///
    /// Time = Effort / 18
    ///
    /// Time to program is calculated for all modules in the current dependency model that are marked with
    /// IsPartOfProject.
    /// </summary>
    /// <returns></returns>
    public double TimeToProgram() {
        return TimeToProgram(_model.getProject());
    }

    /// <summary>
    /// The time to implement or understand a program (T) is proportional to the effort.
    /// Halstead has found that dividing the effort by 18 give an approximation for the time in seconds.
    ///
    /// Time = Effort / 18
    /// </summary>
    /// <param name="project">modules that form the program</param>
    /// <returns></returns>
    public double TimeToProgram(ProjectDescriptor project) {
        double totalEffort = HalsteadEffort(project);
        return totalEffort / 18.0;
    }

    public <T extends Descriptor> double MaintainabilityIndex(T target) {
        //Maintainability Index = MAX(0,(171 - 5.2 * log(Halstead Volume) - 0.23 * (Cyclomatic Complexity) - 16.2 * log(Lines of Code))*100 / 171)

        return 0;
    }
}